Claude/configurable news reader g1 gdx#57
Conversation
… digests Major overhaul of AI-Pulse into a fully configurable, personalized news reader: - config.json: externalized 12 categories (AI, Cybersecurity, IoT, Windows, Mac, Linux, Tech, Entrepreneurship, Finance, Crypto, Open Source, Products) with 112 RSS sources in FR/EN, bilingual labels, and keyword mappings - aggregator.js: config-driven, franc-min language detection, article deduplication (70% Dice similarity), local Readability reader, RSS feed generation (global + per-category), email digests via Resend API - readme-viewer.html: section navigation sidebar with scroll spy, preferences panel (language, categories, keywords, article count slider), DOM-based filtering - reader.html: back button in article info bar - tracker.js: preferences manager, read history, bookmarks manager (localStorage) - All pages: portfolio moved from nav to footer, added "Proposer une source" and "S'abonner" links in footer pointing to GitHub Issue templates - Issue templates: source submission (new-source.yml) and subscription (subscribe.yml) - Workflows: add-source.yml (auto-add approved sources), manage-subscriber.yml (auto-add subscribers), update-ai-pulse.yml (API_RESEND env var) - Email digest: HTML template with personalized content per subscriber preferences https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a13d20ac0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@copilot Corrige le check qui n'a pas marché. |
|
@EthanThePhoenix38 I've opened a new pull request, #58, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This pull request introduces a major upgrade to the AI-Pulse news aggregator, transforming it from a hardcoded system to a fully configurable, multi-language platform with email subscriptions and enhanced user personalization.
Changes:
- Implemented a configuration-driven architecture using config.json with 12 content categories and multi-language support
- Added email digest functionality with subscriber management via GitHub issues and Resend API integration
- Introduced client-side preferences system with language filtering, category selection, keyword search, and read history tracking
- Added RSS feed generation for global and per-category subscriptions
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 31 comments.
Show a summary per file
| File | Description |
|---|---|
| config.json | New centralized configuration defining categories, feeds, language labels, and settings |
| src/aggregator.js | Major refactor: added language detection (franc-min), deduplication, RSS/email generation, and config-driven feed processing |
| templates/email-digest.html | New HTML email template for subscriber digests |
| readme-viewer.html | Enhanced with preferences panel, category filtering, language selection, and read history tracking |
| reader.html | Added back button for improved navigation |
| js/tracker.js | Extended with PrefsManager, ReadHistory, and Bookmarks managers for cross-page persistence |
| package.json | Added franc-min dependency for language detection |
| .github/workflows/manage-subscriber.yml | New workflow to automatically process subscription requests via GitHub issues |
| .github/workflows/add-source.yml | New workflow to automatically add approved RSS sources to config.json |
| .github/workflows/update-ai-pulse.yml | Updated to include API_RESEND secret for email functionality |
| .github/ISSUE_TEMPLATE/*.yml | New issue templates for subscriptions and source suggestions |
| stats.html, privacy.html, index.html, app.html, about.html | Footer updates: moved Portfolio to footer, added subscription/source suggestion links |
| feed*.xml | Generated RSS feed files for global and per-category subscriptions |
| data/subscribers.json | New empty subscribers list file |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const subscriber of subscribers) { | ||
| try { | ||
| // Filter articles based on subscriber preferences | ||
| let filteredSections = []; | ||
| const subCategories = subscriber.categories || Object.keys(CATEGORIES); | ||
| const subLang = subscriber.lang || 'all'; | ||
|
|
||
| for (const cat of subCategories) { | ||
| const articles = categorizedArticles[cat]; | ||
| if (!articles || articles.length === 0) continue; | ||
|
|
||
| let filtered = articles; | ||
| if (subLang !== 'all') { | ||
| filtered = articles.filter(a => a.lang === subLang); | ||
| } | ||
| filtered = filtered.slice(0, 5); | ||
| if (filtered.length === 0) continue; | ||
|
|
||
| const catConfig = CATEGORIES[cat]; | ||
| const label = subLang === 'fr' ? catConfig.labels.fr : catConfig.labels.en; | ||
|
|
||
| const articlesHtml = filtered.map(a => | ||
| `<tr><td style="padding:10px 0;border-bottom:1px solid #1a1e47;"> | ||
| <a href="https://thephoenixagency.github.io/AI-Pulse/app.html?url=${encodeURIComponent(a.link)}&title=${encodeURIComponent(a.title)}&source=${encodeURIComponent(a.source)}" style="color:#00d9ff;text-decoration:none;font-weight:600;">${a.title}</a> | ||
| <br><span style="color:#94a3b8;font-size:13px;">${a.source} | ${a.lang.toUpperCase()}</span> | ||
| </td></tr>` | ||
| ).join(''); | ||
|
|
||
| filteredSections.push(`<tr><td style="padding:20px 0 10px;"><h2 style="color:#00d9ff;margin:0;font-size:18px;">${label}</h2></td></tr>${articlesHtml}`); | ||
| } | ||
|
|
||
| if (filteredSections.length === 0) continue; | ||
|
|
||
| const emailHtml = template | ||
| .replace('{{DATE}}', today) | ||
| .replace('{{SECTIONS}}', filteredSections.join('')) | ||
| .replace('{{EMAIL}}', subscriber.email) | ||
| .replace(/{{UNSUB_URL}}/g, `https://github.com/ThePhoenixAgency/AI-Pulse/issues/new?template=unsubscribe.yml&title=Unsubscribe:+${encodeURIComponent(subscriber.email)}`); | ||
|
|
||
| // Send via Resend | ||
| const response = await axios.post('https://api.resend.com/emails', { | ||
| from: 'AI-Pulse <noreply@resend.dev>', | ||
| to: [subscriber.email], | ||
| subject: `AI-Pulse Digest - ${today}`, | ||
| html: emailHtml | ||
| }, { | ||
| headers: { | ||
| 'Authorization': `Bearer ${apiKey}`, | ||
| 'Content-Type': 'application/json' | ||
| } | ||
| }); | ||
|
|
||
| console.error(` Email sent to ${subscriber.email}: ${response.data.id}`); | ||
| } catch (error) { | ||
| console.error(` Failed to send email to ${subscriber.email}: ${error.message}`); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The email sending loop doesn't implement rate limiting or batch processing. If there are many subscribers, this could hit API rate limits or cause performance issues. Consider implementing batch processing with delays between batches to respect API limits and avoid overwhelming the email service.
| return langMap[code] || code; | ||
| }; | ||
| } catch (e) { | ||
| console.error('franc-min not available, using feed-declared language only'); |
There was a problem hiding this comment.
The franc-min package is used for language detection, but there's no verification that this package is being properly installed. The error handling only logs to stderr but doesn't inform the user how to fix the issue. Consider adding a more informative message suggesting to run "npm install" if the package is missing.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| - type: dropdown | ||
| id: language | ||
| attributes: | ||
| label: Langue preferee / Preferred language |
There was a problem hiding this comment.
The French word "preferee" is missing accents. It should be "préférée".
| label: Langue preferee / Preferred language | |
| label: Langue préférée / Preferred language |
| <h4>Langue / Language</h4> | ||
| <select id="prefLang"> | ||
| <option value="all">Toutes / All</option> | ||
| <option value="fr">Francais</option> |
There was a problem hiding this comment.
The word "Francais" is missing the cedilla. It should be "Français" to be spelled correctly in French.
| <option value="fr">Francais</option> | |
| <option value="fr">Français</option> |
| const lang = detectedLang || feedLang || 'en'; | ||
|
|
||
| // Try to fetch and extract content if it doesn't exist | ||
| if (!fs.existsSync(localPath)) { | ||
| try { | ||
| const response = await axios.get(articleUrl, { timeout: 8000, headers: { 'User-Agent': 'AI-Pulse/2.0' } }); | ||
| const response = await axios.get(articleUrl, { timeout: 8000, headers: { 'User-Agent': 'AI-Pulse/3.0' } }); | ||
| const dom = new JSDOM(response.data, { url: articleUrl }); | ||
| const reader = new Readability(dom.window.document); | ||
| const articleContent = reader.parse(); | ||
|
|
||
| if (articleContent && articleContent.textContent) { | ||
| const cleanHtml = ` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| // Detect language from full content if not yet determined | ||
| if (!detectedLang) { | ||
| detectedLang = detectLang(articleContent.textContent.slice(0, 500)); | ||
| } | ||
|
|
||
| const cleanHtml = `<!DOCTYPE html> | ||
| <html lang="${lang}"> |
There was a problem hiding this comment.
The HTML lang attribute uses a variable that might contain a franc-min language code (ISO 639-3) that hasn't been mapped. The langMap only covers 12 common languages, but franc-min can return many other 3-letter codes. If an unmapped code is returned, it will be set as the lang attribute (e.g., 'pol' for Polish), which is not a valid HTML lang attribute value. HTML requires ISO 639-1 (2-letter) codes. Consider defaulting to 'en' for unmapped codes: const lang = detectedLang || feedLang || 'en'; should check if detectedLang is in a whitelist of valid 2-letter codes first.
| - type: dropdown | ||
| id: category | ||
| attributes: | ||
| label: Categorie / Category |
There was a problem hiding this comment.
The French word "Categorie" is missing an accent. It should be "Catégorie".
| label: Categorie / Category | |
| label: Catégorie / Category |
| - name: Commit changes | ||
| run: | | ||
| git config --global user.name 'PhoenixProject-AutoSync' | ||
| git config --global user.email '${{ secrets.GIT_AUTHOR_EMAIL }}' | ||
| git add config.json | ||
| if ! git diff --cached --exit-code; then | ||
| git commit -m "Add new source from issue #${{ github.event.issue.number }}" | ||
| git push | ||
| fi |
There was a problem hiding this comment.
The workflow has write permissions to contents and issues, and automatically commits changes based on user input from issue forms. A malicious user could potentially craft an issue that, when labeled, causes the workflow to commit malicious content to config.json. While there is a label requirement ('source-approved'), repository maintainers should be extremely careful about which issues they label, as this triggers automatic code commits. Consider adding additional validation steps or requiring manual review before auto-committing, or implement a pull request workflow instead of direct commits.
| - name: Commit changes | |
| run: | | |
| git config --global user.name 'PhoenixProject-AutoSync' | |
| git config --global user.email '${{ secrets.GIT_AUTHOR_EMAIL }}' | |
| git add config.json | |
| if ! git diff --cached --exit-code; then | |
| git commit -m "Add new source from issue #${{ github.event.issue.number }}" | |
| git push | |
| fi | |
| - name: Create pull request for config update | |
| uses: peter-evans/create-pull-request@v6 | |
| with: | |
| commit-message: "Add new source from issue #${{ github.event.issue.number }}" | |
| title: "Add new source from issue #${{ github.event.issue.number }}" | |
| body: | | |
| This pull request was automatically generated from issue #${{ github.event.issue.number }} after it was labeled 'source-approved'. | |
| Please review the changes to config.json before merging. | |
| branch: "auto/add-source-${{ github.event.issue.number }}" | |
| add-paths: | | |
| config.json |
| <!-- Header --> | ||
| <tr><td style="padding:30px 20px;text-align:center;border-bottom:1px solid rgba(0,217,255,0.2);"> | ||
| <h1 style="margin:0;color:#00d9ff;font-size:24px;font-weight:700;">AI-Pulse</h1> | ||
| <p style="margin:8px 0 0;color:#94a3b8;font-size:14px;">Votre digest personnalise - {{DATE}}</p> |
There was a problem hiding this comment.
The email template contains French text "Votre digest personnalise" but "personnalise" is missing the accent on the 'e'. It should be "personnalisé" to be grammatically correct in French.
| Vous recevez ce mail car vous etes abonne a AI-Pulse ({{EMAIL}}). | ||
| </p> | ||
| <p style="margin:0;"> | ||
| <a href="{{UNSUB_URL}}" style="color:#ef4444;font-size:12px;text-decoration:none;">Se desabonner</a> |
There was a problem hiding this comment.
The French text "Se desabonner" is missing an accent. It should be "Se désabonner" for proper French spelling.
|
@codex corrige ce qu'il manque |
|
To use Codex here, create an environment for this repo. |
|
@EthanThePhoenix38 I've opened a new pull request, #59, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@EthanThePhoenix38 I've opened a new pull request, #60, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…as HTML Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…as HTML Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@EthanThePhoenix38 I've opened a new pull request, #61, to work on those changes. Once the pull request is ready, I'll request review from you. |
- Add sticky quick-nav bar for fast section jumping - Improve section visual separation with cards and left border - Add scroll-margin-top for proper anchor positioning https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Remove quick-nav styles (not needed) - Keep improved section separators with cards and left border https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Remove left sidebar navigation - Add fixed top bar with dropdown menu for section navigation - Dropdown shows only filtered categories (or all if no filter) - Center content area without left margin - Keep elevator buttons for scroll up/down - Update responsive styles https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Deduplication improvements: - Compare content (summary) in addition to title - Add contentThreshold config option (default 0.5) - Mark duplicates if title OR content similar, or both moderately similar Read articles improvements: - Normalize URLs for comparison (ignore query params) - Compare by title in addition to URL - Add "Déjà lus / Already read" collapsible group - Add "Hide read articles" preference option - Style read articles group with purple theme https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Add flex-wrap to prevent horizontal overflow - Add max-width and padding for better containment https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Changed ThePhoenixAgency footer link from about.html to https://ThePhoenixAgency.github.io in all pages https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Instead of redirecting to homepage, now goes back to previous page if available, otherwise to the reader https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
The iframe loading ThePhoenixAgency.github.io was blocked due to X-Frame-Options. Replaced with a button link that opens in a new tab. https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Reduce update interval to compensate for GitHub Actions delays https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Remove "Auto-updated every 3 hours" mention - Remove "100% Free & Open Source" from footer - Keep "Last Update" timestamp as needed https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Remove "Proposer une source" link (security vulnerability) - Move ThePhoenixAgency to copyright line - Add mobile CSS grid (3 columns) for footer links - Remove dead link @EthanThePhoenix38 from portfolio https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Update profile image URL - Update GitHub API calls for stats https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
…nix38" This reverts commit 49f7f5e.
- Nouveau guide de configuration (docs/CONFIG_GUIDE.md) - README.md réécrit avec structure complète du projet - index.html documenté avec commentaires détaillés - app.html documenté avec explications du fonctionnement - aggregator.js entièrement commenté (chaque fonction expliquée) Documentation en français, accessible aux débutants, sans jargon technique. https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- css/style.css : Explication des variables, classes, animations - update-ai-pulse.yml : Guide complet du workflow automatique - Explique cron, déclencheurs, étapes, secrets - Documentation accessible aux débutants https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- js/ui.js : Gestion de l'interface (navigation active, curseur) - js/tracker.js : Statistiques locales, préférences, historique, favoris - Explique localStorage et la confidentialité des données - Documente chaque méthode de Tracker, PrefsManager, ReadHistory, Bookmarks Documentation en français accessible aux débutants. https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Conflits résolus: - README.md: Pris la version de main (articles récents) - HTML files: Gardé notre version (documentation + footer corrigé) - aggregator.js: Gardé notre version documentée - workflow: Gardé notre version documentée https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Ajout de commentaires détaillés en français pour : - about.html : Page à propos avec mission et stack technique - privacy.html : Politique de confidentialité (RGPD) - stats.html : Tableau de bord des statistiques locales - 404.html : Page d'erreur avec redirection automatique - reader.html : Lecteur d'articles avec iframe sécurisée Chaque fichier inclut : - Capsule documentaire complète en en-tête - Commentaires CSS et JavaScript expliqués - Descriptions des fonctions et paramètres - Explications accessibles aux débutants https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
Ajout de commentaires détaillés en français pour : - Variables CSS et palette de couleurs - Section héros avec dégradé et liens sociaux - Grille de statistiques GitHub temps réel - Chargement dynamique des projets via API - Protection XSS avec validation des URLs Documentation des fonctions JavaScript : - fetchGitHubStats() : Récupération des données utilisateur - loadFeaturedProjects() : Affichage des projets populaires - escapeHtml() : Protection contre les injections https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
…tibility (#61) Addresses all actionable review feedback from PR #57 focusing on i18n quality, input validation, error handling, and browser compatibility. ## French Localization - Fixed missing accents throughout: `Français`, `Cybersécurité`, `Générale`, `préférée`, `Fréquence`, `Réinitialiser`, etc. - Updated config.json, issue templates, email templates, and UI strings ## Input Validation & Security **Aggregator (`src/aggregator.js`)** - Config loading with actionable error messages on missing/malformed files - Email validation (RFC-compliant regex) before sending - API key format validation with sanitized error output - Deduplication threshold type checking - HTML lang attribute validation (ISO 639-1 codes only) **Workflows** - Email validation with abuse detection (max @ symbol threshold) - RSS URL format validation - Input sanitization for source names and tags (XSS prevention) ```javascript // Before: silent crash on missing config const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); // After: actionable error try { if (!fs.existsSync(CONFIG_PATH)) { console.error(`ERROR: Configuration file not found at ${CONFIG_PATH}`); console.error('Please ensure config.json exists in the repository root.'); process.exit(1); } // ... } catch (e) { console.error(`ERROR: Failed to load or parse config.json: ${e.message}`); process.exit(1); } ``` ## Browser Compatibility - `localStorage` availability checks before all operations - Try-catch wrappers with quota overflow recovery - `IntersectionObserver` feature detection with graceful degradation - `ReadHistory.markRead` safety guard against undefined ## Rate Limiting & Configuration - Email sending rate limit (100ms delay between sends) - Configurable sender via `EMAIL_FROM` env var with dev domain warning - Constants extracted for magic numbers (`VALID_LANG_CODES`, `MAX_SOURCE_NAME_LENGTH`, etc.) ## Security Scan ✅ CodeQL: 0 alerts <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
Nouvelle version du site